package com.ylr.basic.framework.resolver;

import com.ylr.basic.common.constant.BasicConstant;
import com.ylr.base.common.constant.Constant;
import com.ylr.basic.common.dto.EphemeralDataDto;
import com.ylr.basic.common.enums.BusinessTypeEnum;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.tool.Result;
import com.ylr.base.common.utils.DataUtils;
import com.ylr.basic.common.vo.ephemeral_data.EphemeralDataQueryVo;
import com.ylr.basic.common.vo.ephemeral_data.EphemeralDataVo;
import com.ylr.base.framework.service.AsyncService;
import com.ylr.redis.framework.service.RedisService;
import com.alibaba.fastjson2.JSON;
import com.ylr.basic.framework.feign.client.YbEphemeralDataClient;
import com.ylr.redis.framework.service.RedisStringService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-07-07 15:12:34
 * className: YbEphemeralDataResolver
 * version: 1.0
 * description:
 */
public class YbEphemeralDataResolver {

    private static final Logger log = LoggerFactory.getLogger(YbEphemeralDataResolver.class);

    @Value("${spring.application.simple-name:ylr}")
    private String applicationSimpleName;

    private final AsyncService asyncService;

    private final YbEphemeralDataClient ybEphemeralDataClient;

    private final RedisStringService redisStringService;

    private final RedisService redisService;

    public YbEphemeralDataResolver(AsyncService asyncService, YbEphemeralDataClient ybEphemeralDataClient, RedisStringService redisStringService, RedisService redisService) {
        this.asyncService = asyncService;
        this.ybEphemeralDataClient = ybEphemeralDataClient;
        this.redisStringService = redisStringService;
        this.redisService = redisService;
    }

    /**
     * 保存临时数据操作
     * @param businessId       业务主键
     * @param businessTypeEnum 业务类型枚举对象
     * @param businessValue    业务值（敏感数据请提前加密）
     */
    public <T> void saveEphemeralData(Long businessId, BusinessTypeEnum businessTypeEnum, T businessValue) {
        EphemeralDataVo ephemeralDataVo = new EphemeralDataVo();
        ephemeralDataVo.setBusinessId(businessId)
                .setBusinessType(businessTypeEnum.getValue())
                .setBusinessValue(JSON.toJSONString(businessValue));
        Result<Object> result = ybEphemeralDataClient.saveEphemeralData(ephemeralDataVo);
        if (result.isFail()) {
            log.warn("{}模块 => 保存临时数据接口调用异常。message={}", applicationSimpleName, result.getMsg());
            throw new YlrException(result);
        }
        // 过期时间，单位：毫秒
        long expireTime = this.getExpireTime(businessTypeEnum);

        String redisKey = BasicConstant.EPHEMERAL_DATA + businessId + Constant.SPLIT + businessTypeEnum.getValue();

        // 添加缓存
        redisStringService.set(redisKey, ephemeralDataVo.getBusinessValue(), expireTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取临时数据操作
     * @param businessId       业务主键
     * @param businessTypeEnum 业务类型枚举对象
     * @return 结果
     */
    public <T> T getEphemeralData(Long businessId, BusinessTypeEnum businessTypeEnum, Class<T> tClass) {

        String redisKey = BasicConstant.EPHEMERAL_DATA + businessId + Constant.SPLIT + businessTypeEnum.getValue();
        // 1、查询缓存
        String businessValue = redisStringService.get(redisKey);
        if (Objects.nonNull(businessValue)) {
            log.info("从缓存获取");
            return JSON.parseObject(businessValue, tClass);
        }

        // 2、调用服务
        Result<EphemeralDataDto> result = ybEphemeralDataClient.getEphemeralData(businessId, businessTypeEnum.getValue());
        if (result.isFail()) {
            log.warn("{}模块 => 获取临时数据接口调用异常。message={}", applicationSimpleName, result.getMsg());
            throw new YlrException(result);
        }
        // 获取返回结果
        EphemeralDataDto ephemeralDataDto = result.getData();
        if (Objects.isNull(ephemeralDataDto)) {
            log.warn("远程服务返回结果为空");
            return null;
        }
        businessValue = ephemeralDataDto.getBusinessValue();
        if (StringUtils.isNotBlank(businessValue)) {
            log.info("服务返回结果携带的数据为空");
            return null;
        }

        // 计算得到剩余缓存时长，最多缓存一小时
        long expireTime = this.mathExpireTime(businessTypeEnum, ephemeralDataDto.getExpire());

        // 3、添加缓存
        redisStringService.set(redisKey, businessValue, expireTime, TimeUnit.MILLISECONDS);

        return JSON.parseObject(businessValue, tClass);
    }

    /**
     * 批量获取临时数据操作
     * @param businessId        业务主键
     * @param businessTypeEnums 业务类型枚举对象数组
     * @return 结果
     */
    public <T> Map<Integer, T> batchGetEphemeralData(Class<T> tClass, Long businessId, BusinessTypeEnum... businessTypeEnums) {
        Map<Integer, T> res = new HashMap<>();

        StringBuilder sb = new StringBuilder();
        sb.append(BasicConstant.EPHEMERAL_DATA).append(businessId).append(Constant.SPLIT);
        int start = sb.length();
        List<String> redisKeyList = new ArrayList<>(businessTypeEnums.length);
        for (BusinessTypeEnum businessTypeEnum : businessTypeEnums) {
            DataUtils.replaceStringBuilder(sb, start, String.valueOf(businessTypeEnum.getValue()));
            redisKeyList.add(sb.toString());
        }
        // 1、查询缓存
        List<String> valueList = redisStringService.multiGet(redisKeyList);

        List<Integer> businessTypeList = new ArrayList<>();
        // 2、确认缓存返回结果
        for (int i = 0; i < valueList.size(); i++) {
            BusinessTypeEnum businessTypeEnum = businessTypeEnums[i];
            String businessValue = valueList.get(i);
            if (StringUtils.isBlank(businessValue)) {
                businessTypeList.add(businessTypeEnum.getValue());
                continue;
            }
            res.put(businessTypeEnum.getValue(), JSON.parseObject(businessValue, tClass));
        }
        if (businessTypeList.isEmpty()) {
            log.info("从缓存中获取到所有临时数据。map={}", res);
            return res;
        }

        // 3、调用basic服务获取临时数据
        EphemeralDataQueryVo ephemeralDataQueryVo = new EphemeralDataQueryVo();
        ephemeralDataQueryVo.setBusinessId(businessId)
                .setBusinessTypeList(businessTypeList);
        Result<List<EphemeralDataDto>> result = ybEphemeralDataClient.batchGetEphemeralData(ephemeralDataQueryVo);
        if (result.isFail()) {
            log.warn("{}模块 => 批量获取临时数据接口调用异常。message={}", applicationSimpleName, result.getMsg());
            throw new YlrException(result);
        }
        List<EphemeralDataDto> ephemeralDataDtoList = result.getData();
        if (ephemeralDataDtoList.isEmpty()) {
            log.warn("basic服务返回结果为空");
            return res;
        }

        for (EphemeralDataDto ephemeralDataDto : ephemeralDataDtoList) {
            String businessValue = ephemeralDataDto.getBusinessValue();
            if (StringUtils.isBlank(businessValue)) {
                log.info("临时数据为空。businessId={} businessType={}", ephemeralDataDto.getBusinessId(),
                        ephemeralDataDto.getBusinessType());
                continue;
            }
            res.put(ephemeralDataDto.getBusinessType(), JSON.parseObject(businessValue, tClass));
        }
        // 4、添加缓存
        asyncService.runAsync(() -> {
            for (EphemeralDataDto ephemeralDataDto : ephemeralDataDtoList) {
                String businessValue = ephemeralDataDto.getBusinessValue();
                if (StringUtils.isBlank(businessValue)) {
                    continue;
                }
                DataUtils.replaceStringBuilder(sb, start, String.valueOf(ephemeralDataDto.getBusinessType()));
                String redisKey = sb.toString();

                // 计算得到剩余缓存时长，最多缓存一小时
                long expireTime = this.mathExpireTime(BusinessTypeEnum.getBusinessTypeEnum(ephemeralDataDto.getBusinessType()),
                        ephemeralDataDto.getExpire());

                // 添加缓存
                redisStringService.set(redisKey, businessValue, expireTime, TimeUnit.MILLISECONDS);
            }
        });
        return res;
    }

    /**
     * 删除临时数据操作
     * @param businessId   业务主键
     * @param businessType 业务类型
     */
    public void deleteEphemeralData(Long businessId, Integer businessType) {
        // 删除数据
        Result<Object> result = ybEphemeralDataClient.deleteEphemeralData(businessId, businessType);
        if (result.isFail()) {
            log.warn("{}模块 => 删除临时数据接口调用异常。message={}", applicationSimpleName, result.getMsg());
            throw new YlrException(result);
        }

        String redisKey = BasicConstant.EPHEMERAL_DATA + businessId + Constant.SPLIT + businessType;
        // 删除缓存
        redisService.delete(redisKey);
        asyncService.schedule(() -> {
            // 延时双删
            redisService.delete(redisKey);
        }, 1500, TimeUnit.MILLISECONDS);
    }

    /**
     * 计算得到剩余缓存时长
     * @param businessTypeEnum 业务类型枚举对象
     * @param expire           日期对象
     * @return 剩余缓存时长数值，单位：毫秒
     */
    private long mathExpireTime(BusinessTypeEnum businessTypeEnum, Date expire) {
        // 计算得到剩余缓存时长，最多缓存一小时
        long expireTime;
        if (Objects.isNull(expire)) {
            /*
                如果basic服务返回的临时数据过期时间为空
                1、临时数据没有过期时间
                2、没有查询到过期时间
                则根据业务类型枚举对象设置的过期时间进行缓存
             */
            expireTime = this.getExpireTime(businessTypeEnum);
        } else {
            /*
                过期时间戳 - 当前时间戳 = 剩余缓存时长
             */
            expireTime = Math.min(expire.getTime() - System.currentTimeMillis(), 60 * 60 * 1000);
        }
        return expireTime;
    }

    /**
     * 获取过期时间值，单位毫秒
     * @param businessTypeEnum 业务类型枚举对象
     * @return 数值
     */
    private long getExpireTime(BusinessTypeEnum businessTypeEnum) {
        return Optional.ofNullable(businessTypeEnum)
                .map(BusinessTypeEnum::getExpireMillis)
                .filter(expireMills -> expireMills.compareTo(0L) > 0)
                .map(expireMills -> Math.min(expireMills, 60 * 60 * 1000))
                .orElse(60 * 60 * 1000L);
    }
}
